// Copyright Epic Games, Inc. All Rights Reserved.

#include "SCollisionScrollBox_List.h"
#include "Rendering/DrawElements.h"
#include "Types/SlateConstants.h"
#include "Layout/LayoutUtils.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SOverlay.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Images/SImage.h"

namespace Plugin_List
{
	SCollisionScrollBox_List::FSlot& SCollisionScrollBox_List::Slot()
	{
		return *(new SCollisionScrollBox_List::FSlot());
	}

	void SScrollPanel::Construct(const FArguments& InArgs, const TArray<SCollisionScrollBox_List::FSlot*>& InSlots)
	{
		PhysicalOffset = 0;
		Children.Reserve(InSlots.Num());
		for (int32 SlotIndex = 0; SlotIndex < InSlots.Num(); ++SlotIndex)
		{
			Children.Add(InSlots[SlotIndex]);
		}
		Orientation = InArgs._Orientation;
	}

	void SScrollPanel::OnArrangeChildren(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren) const
	{
		float CurChildOffset = -PhysicalOffset;

		for (int32 SlotIndex = 0; SlotIndex < Children.Num(); ++SlotIndex)
		{
			const SCollisionScrollBox_List::FSlot& ThisSlot = Children[SlotIndex];
			const EVisibility ChildVisibility = ThisSlot.GetWidget()->GetVisibility();

			if (ChildVisibility != EVisibility::Collapsed)
			{
				if (Orientation == Orient_Vertical)
				{
					CurChildOffset = ArrangeChildVerticalAndReturnOffset(AllottedGeometry, ArrangedChildren, ThisSlot, CurChildOffset);
				}
				else
				{
					CurChildOffset = ArrangeChildHorizontalAndReturnOffset(AllottedGeometry, ArrangedChildren, ThisSlot, CurChildOffset);
				}
			}
		}
	}

	FVector2D SScrollPanel::ComputeDesiredSize(float) const
	{
		FVector2D ThisDesiredSize = FVector2D::ZeroVector;
		for (int32 SlotIndex = 0; SlotIndex < Children.Num(); ++SlotIndex)
		{
			const SCollisionScrollBox_List::FSlot& ThisSlot = Children[SlotIndex];
			if (ThisSlot.GetWidget()->GetVisibility() != EVisibility::Collapsed)
			{
				const FVector2D ChildDesiredSize = ThisSlot.GetWidget()->GetDesiredSize();
				if (Orientation == Orient_Vertical)
				{
					ThisDesiredSize.X = FMath::Max(ChildDesiredSize.X, ThisDesiredSize.X);
					ThisDesiredSize.Y += ChildDesiredSize.Y + ThisSlot.SlotPadding.Get().GetTotalSpaceAlong<Orient_Vertical>();
				}
				else
				{
					ThisDesiredSize.X += ChildDesiredSize.X + ThisSlot.SlotPadding.Get().GetTotalSpaceAlong<Orient_Horizontal>();
					ThisDesiredSize.Y = FMath::Max(ChildDesiredSize.Y, ThisDesiredSize.Y);
				}
			}
		}

		return ThisDesiredSize;
	}

	float SScrollPanel::ArrangeChildVerticalAndReturnOffset(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren, const SCollisionScrollBox_List::FSlot& ThisSlot, float CurChildOffset) const
	{
		const FMargin& ThisPadding = ThisSlot.SlotPadding.Get();
		const FVector2D& WidgetDesiredSize = ThisSlot.GetWidget()->GetDesiredSize();
		const float ThisSlotDesiredHeight = WidgetDesiredSize.Y + ThisPadding.GetTotalSpaceAlong<Orient_Vertical>();

		// Figure out the size and local position of the child within the slot.  There is no vertical alignment, because 
		// it does not make sense in a panel where items are stacked vertically end-to-end.
		AlignmentArrangeResult XAlignmentResult = AlignChild<Orient_Horizontal>(AllottedGeometry.GetLocalSize().X, ThisSlot, ThisPadding);

		ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(ThisSlot.GetWidget(), FVector2D(XAlignmentResult.Offset, CurChildOffset + ThisPadding.Top), FVector2D(XAlignmentResult.Size, WidgetDesiredSize.Y)));
		return CurChildOffset + ThisSlotDesiredHeight;
	}

	float SScrollPanel::ArrangeChildHorizontalAndReturnOffset(const FGeometry& AllottedGeometry, FArrangedChildren& ArrangedChildren, const SCollisionScrollBox_List::FSlot& ThisSlot, float CurChildOffset) const
	{
		const FMargin& ThisPadding = ThisSlot.SlotPadding.Get();
		const FVector2D& WidgetDesiredSize = ThisSlot.GetWidget()->GetDesiredSize();
		const float ThisSlotDesiredWidth = WidgetDesiredSize.X + ThisPadding.GetTotalSpaceAlong<Orient_Horizontal>();

		// Figure out the size and local position of the child within the slot.  There is no horizontal alignment, because
		// it doesn't make sense in a panel where items are stacked horizontally end-to-end.
		AlignmentArrangeResult YAlignmentResult = AlignChild<Orient_Vertical>(AllottedGeometry.GetLocalSize().Y, ThisSlot, ThisPadding);

		ArrangedChildren.AddWidget(AllottedGeometry.MakeChild(ThisSlot.GetWidget(), FVector2D(CurChildOffset + ThisPadding.Left, YAlignmentResult.Offset), FVector2D(WidgetDesiredSize.X, YAlignmentResult.Size)));
		return CurChildOffset + ThisSlotDesiredWidth;
	}

	SCollisionScrollBox_List::SCollisionScrollBox_List()
	{
		bClippingProxy = true;
	}

	void SCollisionScrollBox_List::Construct( const FArguments& InArgs )
	{
		check(InArgs._Style);

		Style = InArgs._Style;
		ScrollBarStyle = InArgs._ScrollBarStyle;
		DesiredScrollOffset = 0;
		bIsScrolling = false;
		bAnimateScroll = false;
		AmountScrolledWhileRightMouseDown = 0;
		PendingScrollTriggerAmount = 0;
		bShowSoftwareCursor = false;
		SoftwareCursorPosition = FVector2D::ZeroVector;
		OnUserScrolled = InArgs._OnUserScrolled;
		Orientation = InArgs._Orientation;
		bScrollToEnd = false;
		bIsScrollingActiveTimerRegistered = false;
		bAllowsRightClickDragScrolling = false;
		ConsumeMouseWheel = InArgs._ConsumeMouseWheel;
		TickScrollDelta = 0;
		AllowOverscroll = InArgs._AllowOverscroll;
		bAnimateWheelScrolling = InArgs._AnimateWheelScrolling;
		WheelScrollMultiplier = InArgs._WheelScrollMultiplier;
		NavigationScrollPadding = InArgs._NavigationScrollPadding;
		NavigationDestination = InArgs._NavigationDestination;
		ScrollWhenFocusChanges = InArgs._ScrollWhenFocusChanges;
		bTouchPanningCapture = false;
		bVolatilityAlwaysInvalidatesPrepass = true;

		if (InArgs._ExternalScrollbar.IsValid())
		{
			// An external scroll bar was specified by the user
			ScrollBar = InArgs._ExternalScrollbar;
			ScrollBar->SetOnUserScrolled(FOnUserScrolled::CreateSP(this, &SCollisionScrollBox_List::ScrollBar_OnUserScrolled));
			bScrollBarIsExternal = true;
		}
		else
		{
			// Make a scroll bar 
			ScrollBar = ConstructScrollBar();
			ScrollBar->SetDragFocusCause(InArgs._ScrollBarDragFocusCause);
			ScrollBar->SetThickness(InArgs._ScrollBarThickness);
			ScrollBar->SetPadding(InArgs._ScrollBarPadding);
			ScrollBar->SetUserVisibility(InArgs._ScrollBarVisibility);
			ScrollBar->SetScrollBarAlwaysVisible(InArgs._ScrollBarAlwaysVisible);

			bScrollBarIsExternal = false;
		}

		SAssignNew(ScrollPanel, SScrollPanel, InArgs.Slots)
			.Clipping(InArgs._Clipping)
			.Orientation(Orientation);

		if (Orientation == Orient_Vertical)
		{
			ConstructVerticalLayout();
		}
		else
		{
			ConstructHorizontalLayout();
		}

		ScrollBar->SetState( 0.0f, 1.0f );
	}

	void SCollisionScrollBox_List::OnClippingChanged()
	{
		ScrollPanel->SetClipping(Clipping);
	}

	TSharedPtr<SScrollBar> SCollisionScrollBox_List::ConstructScrollBar()
	{
		return TSharedPtr<SScrollBar>(SNew(SScrollBar)
			.Style(ScrollBarStyle)
			.Orientation(Orientation)
			.OnUserScrolled(this, &SCollisionScrollBox_List::ScrollBar_OnUserScrolled));
	}

	void SCollisionScrollBox_List::ConstructVerticalLayout()
	{
		TSharedPtr<SHorizontalBox> PanelAndScrollbar;
		this->ChildSlot
		[
			SAssignNew(PanelAndScrollbar, SHorizontalBox)

			+ SHorizontalBox::Slot()
			.FillWidth(1)
			[
				SNew(SOverlay)

				+ SOverlay::Slot()
				.Padding(FMargin(0.0f, 0.0f, 0.0f, 1.0f))
				[
					// Scroll panel that presents the scrolled content
					ScrollPanel.ToSharedRef()
				]

				+ SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Top)
				[
					// Shadow: Hint to scroll up
					SNew(SImage)
					.Visibility(EVisibility::Collapsed)
					.ColorAndOpacity(this, &SCollisionScrollBox_List::GetStartShadowOpacity)
					.Image(&Style->TopShadowBrush)
				]

				+ SOverlay::Slot()
				.HAlign(HAlign_Fill)
				.VAlign(VAlign_Bottom)
				[
					// Shadow: a hint to scroll down
					SNew(SImage)
					.Visibility(EVisibility::Collapsed)
					.ColorAndOpacity(this, &SCollisionScrollBox_List::GetEndShadowOpacity)
					.Image(&Style->BottomShadowBrush)
				]
			]
		];

		if (!bScrollBarIsExternal)
		{
			PanelAndScrollbar->AddSlot()
			.AutoWidth()
			[
				ScrollBar.ToSharedRef()
			];
		}
	}

	void SCollisionScrollBox_List::ConstructHorizontalLayout()
	{
		TSharedPtr<SVerticalBox> PanelAndScrollbar;
		this->ChildSlot
		[
			SAssignNew(PanelAndScrollbar, SVerticalBox)

			+ SVerticalBox::Slot()
			.FillHeight(1)
			[
				SNew(SOverlay)

				+ SOverlay::Slot()
				.Padding(FMargin(0.0f, 0.0f, 1.0f, 0.0f))
				[
					// Scroll panel that presents the scrolled content
					ScrollPanel.ToSharedRef()
				]

				+ SOverlay::Slot()
				.HAlign(HAlign_Left)
				.VAlign(VAlign_Fill)
				[
					// Shadow: Hint to left
					SNew(SImage).Visibility(EVisibility::Collapsed)
					.ColorAndOpacity(this, &SCollisionScrollBox_List::GetStartShadowOpacity)
					.Image(&Style->LeftShadowBrush)
				]

				+ SOverlay::Slot()
				.HAlign(HAlign_Right)
				.VAlign(VAlign_Fill)
				[
					// Shadow: a hint to scroll right
					SNew(SImage).Visibility(EVisibility::Collapsed)
					.ColorAndOpacity(this, &SCollisionScrollBox_List::GetEndShadowOpacity)
					.Image(&Style->RightShadowBrush)
				]
			]
		];

		if (!bScrollBarIsExternal)
		{
			PanelAndScrollbar->AddSlot()
				.AutoHeight()
				[
					ScrollBar.ToSharedRef()
				];
		}
	}

	/** Adds a slot to SCollisionScrollBox_List */
	SCollisionScrollBox_List::FSlot& SCollisionScrollBox_List::AddSlot()
	{
		SCollisionScrollBox_List::FSlot& NewSlot = *new SCollisionScrollBox_List::FSlot();
		ScrollPanel->Children.Add( &NewSlot );

		return NewSlot;
	}

	/** Removes a slot at the specified location */
	void SCollisionScrollBox_List::RemoveSlot( const TSharedRef<SWidget>& WidgetToRemove )
	{
		TPanelChildren<SCollisionScrollBox_List::FSlot>& Children = ScrollPanel->Children;
		for( int32 SlotIndex=0; SlotIndex < Children.Num(); ++SlotIndex )
		{
			if ( Children[SlotIndex].GetWidget() == WidgetToRemove )
			{
				Children.RemoveAt(SlotIndex);
				return;
			}
		}
	}

	void SCollisionScrollBox_List::ClearChildren()
	{
		ScrollPanel->Children.Empty();
	}

	bool SCollisionScrollBox_List::IsRightClickScrolling() const
	{
		return FSlateApplication::IsInitialized() && AmountScrolledWhileRightMouseDown >= FSlateApplication::Get().GetDragTriggerDistance() && this->ScrollBar->IsNeeded();
	}

	float SCollisionScrollBox_List::GetScrollOffset() const
	{
		return DesiredScrollOffset;
	}

	float SCollisionScrollBox_List::GetScrollOffsetOfEnd() const
	{
		const FGeometry ScrollPanelGeometry = FindChildGeometry(CachedGeometry, ScrollPanel.ToSharedRef());
		const float ContentSize = GetScrollComponentFromVector(ScrollPanel->GetDesiredSize());
		return FMath::Max(ContentSize - GetScrollComponentFromVector(ScrollPanelGeometry.Size), 0.0f);
	}

	float SCollisionScrollBox_List::GetViewFraction() const
	{
		const FGeometry ScrollPanelGeometry = FindChildGeometry(CachedGeometry, ScrollPanel.ToSharedRef());
		const float ContentSize = GetScrollComponentFromVector(ScrollPanel->GetDesiredSize());

		return FMath::Clamp<float>(GetScrollComponentFromVector(CachedGeometry.GetLocalSize()) > 0 ? GetScrollComponentFromVector(ScrollPanelGeometry.Size) / ContentSize : 1, 0.0f, 1.0f);
	}

	float SCollisionScrollBox_List::GetViewOffsetFraction() const
	{
		const FGeometry ScrollPanelGeometry = FindChildGeometry(CachedGeometry, ScrollPanel.ToSharedRef());
		const float ContentSize = GetScrollComponentFromVector(ScrollPanel->GetDesiredSize());

		const float ViewFraction = GetViewFraction();
		return FMath::Clamp<float>( DesiredScrollOffset/ContentSize, 0.0, 1.0 - ViewFraction );
	}

	void SCollisionScrollBox_List::SetScrollOffset( float NewScrollOffset )
	{
		DesiredScrollOffset = NewScrollOffset;
		bScrollToEnd = false;

		Invalidate(EInvalidateWidget::Layout);
	}

	void SCollisionScrollBox_List::ScrollToStart()
	{
		SetScrollOffset(0);
	}

	void SCollisionScrollBox_List::ScrollToEnd()
	{
		bScrollToEnd = true;

		Invalidate(EInvalidateWidget::Layout);
	}

	void SCollisionScrollBox_List::ScrollDescendantIntoView(const TSharedPtr<SWidget>& WidgetToScrollIntoView, bool InAnimateScroll, EDescendantScrollDestination InDestination, float InScrollPadding)
	{
		ScrollIntoViewRequest = [this, WidgetToScrollIntoView, InAnimateScroll, InDestination, InScrollPadding] (FGeometry AllottedGeometry) {
			InternalScrollDescendantIntoView(AllottedGeometry, WidgetToScrollIntoView, InAnimateScroll, InDestination, InScrollPadding);
		};

		BeginInertialScrolling();
	}

	bool SCollisionScrollBox_List::InternalScrollDescendantIntoView(const FGeometry& MyGeometry, const TSharedPtr<SWidget>& WidgetToFind, bool InAnimateScroll, EDescendantScrollDestination InDestination, float InScrollPadding)
	{
		// We need to safely find the one WidgetToFind among our descendants.
		TSet< TSharedRef<SWidget> > WidgetsToFind;
		{
			if (WidgetToFind.IsValid())
			{
				WidgetsToFind.Add(WidgetToFind.ToSharedRef());
			}
		}
		TMap<TSharedRef<SWidget>, FArrangedWidget> Result;

		FindChildGeometries( MyGeometry, WidgetsToFind, Result );

		if (WidgetToFind.IsValid())
		{
			FArrangedWidget* WidgetGeometry = Result.Find(WidgetToFind.ToSharedRef());
			if (!WidgetGeometry)
			{
				UE_LOG(LogSlate, Warning, TEXT("Unable to scroll to descendant as it's not a child of the scrollbox"));
			}
			else
			{
				float ScrollOffset = 0.0f;
				if (InDestination == EDescendantScrollDestination::TopOrLeft)
				{
					// Calculate how much we would need to scroll to bring this to the top/left of the scroll box
					const float WidgetPosition = GetScrollComponentFromVector(MyGeometry.AbsoluteToLocal(WidgetGeometry->Geometry.GetAbsolutePosition()));
					const float MyPosition = InScrollPadding;
					ScrollOffset = WidgetPosition - MyPosition;
				}
				else if (InDestination == EDescendantScrollDestination::Center)
				{
					// Calculate how much we would need to scroll to bring this to the top/left of the scroll box
					const float WidgetPosition = GetScrollComponentFromVector(MyGeometry.AbsoluteToLocal(WidgetGeometry->Geometry.GetAbsolutePosition()) + (WidgetGeometry->Geometry.GetLocalSize() / 2));
					const float MyPosition = GetScrollComponentFromVector(MyGeometry.GetLocalSize() * FVector2D(0.5f, 0.5f));
					ScrollOffset = WidgetPosition - MyPosition;
				}
				else
				{
					const float WidgetStartPosition = GetScrollComponentFromVector(MyGeometry.AbsoluteToLocal(WidgetGeometry->Geometry.GetAbsolutePosition()));
					const float WidgetEndPosition = WidgetStartPosition + GetScrollComponentFromVector(WidgetGeometry->Geometry.GetLocalSize());
					const float ViewStartPosition = InScrollPadding;
					const float ViewEndPosition = GetScrollComponentFromVector(MyGeometry.GetLocalSize() - InScrollPadding);

					const float ViewDelta = (ViewEndPosition - ViewStartPosition);
					const float WidgetDelta = (WidgetEndPosition - WidgetStartPosition);

					if (WidgetStartPosition < ViewStartPosition)
					{
						ScrollOffset = WidgetStartPosition - ViewStartPosition;
					}
					else if (WidgetEndPosition > ViewEndPosition)
					{
						ScrollOffset = (WidgetEndPosition - ViewDelta) - ViewStartPosition;
					}
				}

				if (ScrollOffset != 0.0f)
				{
					DesiredScrollOffset = ScrollPanel->PhysicalOffset;
					ScrollBy(MyGeometry, ScrollOffset, EAllowOverscroll::No, InAnimateScroll);
				}

				return true;
			}
		}

		return false;
	}

	EOrientation SCollisionScrollBox_List::GetOrientation()
	{
		return Orientation;
	}

	void SCollisionScrollBox_List::SetConsumeMouseWheel(EConsumeMouseWheel NewConsumeMouseWheel)
	{
		ConsumeMouseWheel = NewConsumeMouseWheel;
	}

	void SCollisionScrollBox_List::SetOrientation(EOrientation InOrientation)
	{
		if (Orientation != InOrientation)
		{
			Orientation = InOrientation;
			if (!bScrollBarIsExternal)
			{
				ScrollBar = ConstructScrollBar();
			}
			ScrollPanel->SetOrientation(Orientation);
			if (Orientation == Orient_Vertical)
			{
				ConstructVerticalLayout();
			}
			else
			{
				ConstructHorizontalLayout();
			}
		}
	}

	void SCollisionScrollBox_List::SetScrollBarVisibility(EVisibility InVisibility)
	{
		ScrollBar->SetUserVisibility(InVisibility);
	}

	void SCollisionScrollBox_List::SetScrollBarAlwaysVisible(bool InAlwaysVisible)
	{
		ScrollBar->SetScrollBarAlwaysVisible(InAlwaysVisible);
	}

	void SCollisionScrollBox_List::SetScrollBarTrackAlwaysVisible(bool InAlwaysVisible)
	{
		ScrollBar->SetScrollBarTrackAlwaysVisible(InAlwaysVisible);
	}

	void SCollisionScrollBox_List::SetScrollBarThickness(FVector2D InThickness)
	{
		ScrollBar->SetThickness(InThickness);
	}

	void SCollisionScrollBox_List::SetScrollBarPadding(const FMargin& InPadding)
	{
		ScrollBar->SetPadding(InPadding);
	}

	void SCollisionScrollBox_List::SetScrollBarRightClickDragAllowed(bool bIsAllowed)
	{
		bAllowsRightClickDragScrolling = bIsAllowed;
	}

	EActiveTimerReturnType SCollisionScrollBox_List::UpdateInertialScroll(double InCurrentTime, float InDeltaTime)
	{
		bool bKeepTicking = bIsScrolling;

		if ( bIsScrolling )
		{
			InertialScrollManager.UpdateScrollVelocity(InDeltaTime);
			const float ScrollVelocityLocal = InertialScrollManager.GetScrollVelocity() / CachedGeometry.Scale;

			if (ScrollVelocityLocal != 0.f )
			{
				if ( CanUseInertialScroll(ScrollVelocityLocal) )
				{
					bKeepTicking = true;
					ScrollBy(CachedGeometry, ScrollVelocityLocal * InDeltaTime, AllowOverscroll, false);
				}
				else
				{
					InertialScrollManager.ClearScrollVelocity();
				}
			}
		}

		if ( AllowOverscroll == EAllowOverscroll::Yes )
		{
			// If we are currently in overscroll, the list will need refreshing.
			// Do this before UpdateOverscroll, as that could cause GetOverscroll() to be 0
			if ( Overscroll.GetOverscroll(CachedGeometry) != 0.0f )
			{
				bKeepTicking = true;
			}

			Overscroll.UpdateOverscroll(InDeltaTime);
		}

		TickScrollDelta = 0.f;

		if ( !bKeepTicking )
		{
			bIsScrolling = false;
			bIsScrollingActiveTimerRegistered = false;
			Invalidate(EInvalidateWidget::LayoutAndVolatility);
			UpdateInertialScrollHandle.Reset();
		}

		return bKeepTicking ? EActiveTimerReturnType::Continue : EActiveTimerReturnType::Stop;
	}

	void SCollisionScrollBox_List::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
	{
		CachedGeometry = AllottedGeometry;

		if ( bTouchPanningCapture && (FSlateApplication::Get().GetCurrentTime() - LastScrollTime) > 0.10 )
		{
			InertialScrollManager.ClearScrollVelocity();
		}

		// If we needed a widget to be scrolled into view, make that happen.
		if ( ScrollIntoViewRequest )
		{
			ScrollIntoViewRequest(AllottedGeometry);
			ScrollIntoViewRequest = nullptr;
		}

		const FGeometry ScrollPanelGeometry = FindChildGeometry( AllottedGeometry, ScrollPanel.ToSharedRef() );
		const float ContentSize = GetScrollComponentFromVector(ScrollPanel->GetDesiredSize());

		if ( bScrollToEnd )
		{
			DesiredScrollOffset = FMath::Max(ContentSize - GetScrollComponentFromVector(ScrollPanelGeometry.GetLocalSize()), 0.0f);
			bScrollToEnd = false;
		}

		// If this scroll box has no size, do not compute a view fraction because it will be wrong and causes pop in when the size is available
		const float ViewFraction = GetViewFraction();
		const float TargetViewOffset = GetViewOffsetFraction();
	
		const float CurrentViewOffset = bAnimateScroll ? FMath::FInterpTo(ScrollBar->DistanceFromTop(), TargetViewOffset, InDeltaTime, 15.f) : TargetViewOffset;

		// Update the scrollbar with the clamped version of the offset
		float NewPhysicalOffset = GetScrollComponentFromVector(CurrentViewOffset * ScrollPanel->GetDesiredSize());
		if ( AllowOverscroll == EAllowOverscroll::Yes )
		{
			NewPhysicalOffset += Overscroll.GetOverscroll(AllottedGeometry);
		}

		const bool bWasScrolling = bIsScrolling;
		bIsScrolling = !FMath::IsNearlyEqual(NewPhysicalOffset, ScrollPanel->PhysicalOffset, 0.001f);

		ScrollPanel->PhysicalOffset = NewPhysicalOffset;
	
		if (bWasScrolling && !bIsScrolling)
		{
			Invalidate(EInvalidateWidget::Layout);
		}
	
		ScrollBar->SetState(CurrentViewOffset, ViewFraction);
		if (!ScrollBar->IsNeeded())
		{
			// We cannot scroll, so ensure that there is no offset.
			ScrollPanel->PhysicalOffset = 0.0f;
		}
	}

	bool SCollisionScrollBox_List::ComputeVolatility() const
	{
		return bIsScrolling || IsRightClickScrolling();
	}

	FReply SCollisionScrollBox_List::OnPreviewMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
	{
		if (MouseEvent.IsTouchEvent() && !bFingerOwningTouchInteraction.IsSet())
		{
			// Clear any inertia 
			InertialScrollManager.ClearScrollVelocity();
			// We have started a new interaction; track how far the user has moved since they put their finger down.
			AmountScrolledWhileRightMouseDown = 0;
			PendingScrollTriggerAmount = 0;
			// Someone put their finger down in this list, so they probably want to drag the list.
			bFingerOwningTouchInteraction = MouseEvent.GetPointerIndex();

			Invalidate(EInvalidateWidget::Layout);
		}
		return FReply::Unhandled();
	}

	FReply SCollisionScrollBox_List::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
	{
		if ( !bFingerOwningTouchInteraction.IsSet() )
		{
			EndInertialScrolling();
		}

		if ( MouseEvent.IsTouchEvent() )
		{
			return FReply::Handled();
		}
		else
		{
			if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && ScrollBar->IsNeeded()  && bAllowsRightClickDragScrolling)
			{
				AmountScrolledWhileRightMouseDown = 0;

				Invalidate(EInvalidateWidget::Layout);

				return FReply::Handled();
			}
		}

		return FReply::Unhandled();	
	}

	FReply SCollisionScrollBox_List::OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
	{
		if ( MouseEvent.GetEffectingButton() == EKeys::RightMouseButton && bAllowsRightClickDragScrolling)
		{
			if ( !bIsScrollingActiveTimerRegistered && IsRightClickScrolling() )
			{
				// Register the active timer to handle the inertial scrolling
				CachedGeometry = MyGeometry;
				BeginInertialScrolling();
			}

			AmountScrolledWhileRightMouseDown = 0;

			Invalidate(EInvalidateWidget::Layout);

			FReply Reply = FReply::Handled().ReleaseMouseCapture();
			bShowSoftwareCursor = false;

			// If we have mouse capture, snap the mouse back to the closest location that is within the panel's bounds
			if ( HasMouseCapture() )
			{
				FSlateRect PanelScreenSpaceRect = MyGeometry.GetLayoutBoundingRect();
				FVector2D CursorPosition = MyGeometry.LocalToAbsolute( SoftwareCursorPosition );

				FIntPoint BestPositionInPanel(
					FMath::RoundToInt( FMath::Clamp( CursorPosition.X, PanelScreenSpaceRect.Left, PanelScreenSpaceRect.Right ) ),
					FMath::RoundToInt( FMath::Clamp( CursorPosition.Y, PanelScreenSpaceRect.Top, PanelScreenSpaceRect.Bottom ) )
					);

				Reply.SetMousePos(BestPositionInPanel);
			}

			return Reply;
		}

		return FReply::Unhandled();
	}

	FReply SCollisionScrollBox_List::OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
	{
		const float ScrollByAmountScreen = GetScrollComponentFromVector(MouseEvent.GetCursorDelta());
		const float ScrollByAmountLocal = ScrollByAmountScreen / MyGeometry.Scale;

		if ( MouseEvent.IsTouchEvent() )
		{
			FReply Reply = FReply::Unhandled();

			if ( !bTouchPanningCapture )
			{
				if ( bFingerOwningTouchInteraction.IsSet() && MouseEvent.IsTouchEvent() && !HasMouseCapture() )
				{
					PendingScrollTriggerAmount += ScrollByAmountScreen;

					if ( FMath::Abs(PendingScrollTriggerAmount) > FSlateApplication::Get().GetDragTriggerDistance() )
					{
						bTouchPanningCapture = true;
						ScrollBar->BeginScrolling();

						// The user has moved the list some amount; they are probably
						// trying to scroll. From now on, the list assumes the user is scrolling
						// until they lift their finger.
						Reply = FReply::Handled().CaptureMouse(AsShared());
					}
					else
					{
						Reply = FReply::Handled();
					}
				}
			}
			else
			{
				if ( bFingerOwningTouchInteraction.IsSet() && HasMouseCaptureByUser(MouseEvent.GetUserIndex(), MouseEvent.GetPointerIndex()) )
				{
					LastScrollTime = FSlateApplication::Get().GetCurrentTime();
					InertialScrollManager.AddScrollSample(-ScrollByAmountScreen, FSlateApplication::Get().GetCurrentTime());
					ScrollBy(MyGeometry, -ScrollByAmountLocal, EAllowOverscroll::Yes, false);

					Reply = FReply::Handled();
				}
			}

			return Reply;
		}
		else
		{
			if ( MouseEvent.IsMouseButtonDown(EKeys::RightMouseButton)  && bAllowsRightClickDragScrolling)
			{
				// If scrolling with the right mouse button, we need to remember how much we scrolled.
				// If we did not scroll at all, we will bring up the context menu when the mouse is released.
				AmountScrolledWhileRightMouseDown += FMath::Abs(ScrollByAmountScreen);

				// Has the mouse moved far enough with the right mouse button held down to start capturing
				// the mouse and dragging the view?
				if ( IsRightClickScrolling() )
				{
					InertialScrollManager.AddScrollSample(-ScrollByAmountScreen, FPlatformTime::Seconds());
					const bool bDidScroll = ScrollBy(MyGeometry, -ScrollByAmountLocal, AllowOverscroll, false);

					FReply Reply = FReply::Handled();

					// Capture the mouse if we need to
					if ( HasMouseCapture() == false )
					{
						Reply.CaptureMouse(AsShared()).UseHighPrecisionMouseMovement(AsShared());
						SoftwareCursorPosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
						bShowSoftwareCursor = true;
					}

					// Check if the mouse has moved.
					if ( bDidScroll )
					{
						SetScrollComponentOnVector(SoftwareCursorPosition, GetScrollComponentFromVector(SoftwareCursorPosition) + GetScrollComponentFromVector(MouseEvent.GetCursorDelta()));
					}

					return Reply;
				}
			}
		}

		return FReply::Unhandled();
	}

	void SCollisionScrollBox_List::OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
	{
		if ( MouseEvent.IsTouchEvent() )
		{
			if ( !bFingerOwningTouchInteraction.IsSet() )
			{
				// If we currently do not have touch capture, allow this widget to begin scrolling on pointer enter events
				// if it comes from a child widget
				if ( MyGeometry.IsUnderLocation(MouseEvent.GetLastScreenSpacePosition()) )
				{
					bFingerOwningTouchInteraction = MouseEvent.GetPointerIndex();
				}
			}
		}
	}

	void SCollisionScrollBox_List::OnMouseLeave( const FPointerEvent& MouseEvent )
	{
		if ( HasMouseCapture() == false )
		{
			// No longer scrolling (unless we have mouse capture)
			if ( AmountScrolledWhileRightMouseDown != 0 )
			{
				AmountScrolledWhileRightMouseDown = 0;
				Invalidate(EInvalidateWidget::Layout);
			}

			if ( MouseEvent.IsTouchEvent() )
			{
				bFingerOwningTouchInteraction.Reset();
			}
		}
	}

	FReply SCollisionScrollBox_List::OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent )
	{
		if ((ScrollBar->IsNeeded() && ConsumeMouseWheel != EConsumeMouseWheel::Never) || ConsumeMouseWheel == EConsumeMouseWheel::Always)
		{
			// Make sure scroll velocity is cleared so it doesn't fight with the mouse wheel input
			InertialScrollManager.ClearScrollVelocity();

			const bool bScrollWasHandled = ScrollBy(MyGeometry, -MouseEvent.GetWheelDelta() * GetGlobalScrollAmount() * WheelScrollMultiplier, EAllowOverscroll::No, bAnimateWheelScrolling);

			if ( bScrollWasHandled && !bIsScrollingActiveTimerRegistered )
			{
				// Register the active timer to handle the inertial scrolling
				CachedGeometry = MyGeometry;
				BeginInertialScrolling();
			}

			return FReply::Handled();
			//这里修改过
			//return bScrollWasHandled ? FReply::Handled() : FReply::Unhandled();
		}
		else
		{
			//这里修改过
			//return FReply::Unhandled();
			return FReply::Handled();
		}
	}

	bool SCollisionScrollBox_List::ScrollBy(const FGeometry& AllottedGeometry, float LocalScrollAmount, EAllowOverscroll Overscrolling, bool InAnimateScroll)
	{
		Invalidate(EInvalidateWidget::LayoutAndVolatility);

		bAnimateScroll = InAnimateScroll;

		const float ContentSize = GetScrollComponentFromVector(ScrollPanel->GetDesiredSize());
		const FGeometry ScrollPanelGeometry = FindChildGeometry( AllottedGeometry, ScrollPanel.ToSharedRef() );

		const float PreviousScrollOffset = DesiredScrollOffset;

		if (LocalScrollAmount != 0 )
		{
			const float ScrollMin = 0.0f;
			const float ScrollMax = FMath::Max(ContentSize - GetScrollComponentFromVector(ScrollPanelGeometry.GetLocalSize()), 0.0f);

			if ( AllowOverscroll == EAllowOverscroll::Yes && Overscrolling == EAllowOverscroll::Yes && Overscroll.ShouldApplyOverscroll(DesiredScrollOffset == 0, DesiredScrollOffset == ScrollMax, LocalScrollAmount) )
			{
				Overscroll.ScrollBy(AllottedGeometry, LocalScrollAmount);
			}
			else
			{
				DesiredScrollOffset = FMath::Clamp(DesiredScrollOffset + LocalScrollAmount, ScrollMin, ScrollMax);
			}
		}

		OnUserScrolled.ExecuteIfBound(DesiredScrollOffset);

		return ConsumeMouseWheel == EConsumeMouseWheel::Always || DesiredScrollOffset != PreviousScrollOffset;
	}

	FCursorReply SCollisionScrollBox_List::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) const
	{
		if ( IsRightClickScrolling() )
		{
			// We hide the native cursor as we'll be drawing the software EMouseCursor::GrabHandClosed cursor
			return FCursorReply::Cursor( EMouseCursor::None );
		}
		else
		{
			return FCursorReply::Unhandled();
		}
	}

	FReply SCollisionScrollBox_List::OnTouchEnded(const FGeometry& MyGeometry, const FPointerEvent& InTouchEvent)
	{
		CachedGeometry = MyGeometry;

		if ( HasMouseCaptureByUser(InTouchEvent.GetUserIndex(), InTouchEvent.GetPointerIndex()) )
		{
			ScrollBar->EndScrolling();
			Invalidate(EInvalidateWidget::Layout);
		
			BeginInertialScrolling();

			return FReply::Handled().ReleaseMouseCapture();
		}

		return FReply::Unhandled();
	}

	void SCollisionScrollBox_List::OnMouseCaptureLost(const FCaptureLostEvent& CaptureLostEvent)
	{
		SCompoundWidget::OnMouseCaptureLost(CaptureLostEvent);
		AmountScrolledWhileRightMouseDown = 0;
		PendingScrollTriggerAmount = 0;
		bFingerOwningTouchInteraction.Reset();
		bTouchPanningCapture = false;

	}

	FNavigationReply SCollisionScrollBox_List::OnNavigation(const FGeometry& MyGeometry, const FNavigationEvent& InNavigationEvent)
	{
		TSharedPtr<SWidget> FocusedChild;
		int32 FocusedChildIndex = -1;
		int32 FocusedChildDirection = 0;

		// Find the child with focus currently so that we can find the next logical child we're going to move to.
		TPanelChildren<SCollisionScrollBox_List::FSlot>& Children = ScrollPanel->Children;
		for ( int32 SlotIndex=0; SlotIndex < Children.Num(); ++SlotIndex )
		{
			if ( Children[SlotIndex].GetWidget()->HasUserFocus(InNavigationEvent.GetUserIndex()).IsSet() ||
				 Children[SlotIndex].GetWidget()->HasUserFocusedDescendants(InNavigationEvent.GetUserIndex()) )
			{
				FocusedChild = Children[SlotIndex].GetWidget();
				FocusedChildIndex = SlotIndex;
				break;
			}
		}

		if ( FocusedChild.IsValid() )
		{
			if ( Orientation == Orient_Vertical )
			{
				switch ( InNavigationEvent.GetNavigationType() )
				{
				case EUINavigation::Up:
					FocusedChildDirection = -1;
					break;
				case EUINavigation::Down:
					FocusedChildDirection = 1;
					break;
				default:
					// If we don't handle this direction in our current orientation we can 
					// just allow the behavior of the boundary rule take over.
					return SCompoundWidget::OnNavigation(MyGeometry, InNavigationEvent);
				}
			}
			else // Orient_Horizontal
				{
				switch ( InNavigationEvent.GetNavigationType() )
				{
				case EUINavigation::Left:
					FocusedChildDirection = -1;
					break;
				case EUINavigation::Right:
					FocusedChildDirection = 1;
					break;
				default:
					// If we don't handle this direction in our current orientation we can 
					// just allow the behavior of the boundary rule take over.
					return SCompoundWidget::OnNavigation(MyGeometry, InNavigationEvent);
				}
				}

			// If the focused child index is in a valid range we know we can successfully focus
			// the new child we're moving focus to.
			if ( FocusedChildDirection != 0 )
			{
				TSharedPtr<SWidget> NextFocusableChild;

				// Search in the direction we need to move for the next focusable child of the scrollbox.
				for ( int32 ChildIndex = FocusedChildIndex + FocusedChildDirection; ChildIndex >= 0 && ChildIndex < Children.Num(); ChildIndex += FocusedChildDirection )
				{
					TSharedPtr<SWidget> PossiblyFocusableChild = GetKeyboardFocusableWidget(Children[ChildIndex].GetWidget());
					if ( PossiblyFocusableChild.IsValid() )
					{
						NextFocusableChild = PossiblyFocusableChild;
						break;
					}
				}

				// If we found a focusable child, scroll to it, and shift focus.
				if ( NextFocusableChild.IsValid() )
				{
					InternalScrollDescendantIntoView(MyGeometry, NextFocusableChild, false, NavigationDestination, NavigationScrollPadding);
					return FNavigationReply::Explicit(NextFocusableChild);
				}
			}
		}

		return SCompoundWidget::OnNavigation(MyGeometry, InNavigationEvent);
	}

	void SCollisionScrollBox_List::OnFocusChanging(const FWeakWidgetPath& PreviousFocusPath, const FWidgetPath& NewWidgetPath, const FFocusEvent& InFocusEvent)
	{
		if (ScrollWhenFocusChanges != EScrollWhenFocusChanges::NoScroll)
		{
			if (NewWidgetPath.IsValid() && NewWidgetPath.ContainsWidget(SharedThis(this)))
			{
				ScrollDescendantIntoView(NewWidgetPath.GetLastWidget(), ScrollWhenFocusChanges == EScrollWhenFocusChanges::AnimatedScroll ? true : false, NavigationDestination, NavigationScrollPadding);
			}
		}
	}

	TSharedPtr<SWidget> SCollisionScrollBox_List::GetKeyboardFocusableWidget(TSharedPtr<SWidget> InWidget)
	{
		if (EVisibility::DoesVisibilityPassFilter(InWidget->GetVisibility(), EVisibility::Visible))
		{
			if (InWidget->SupportsKeyboardFocus())
			{
				return InWidget;
			}
			else
			{
				FChildren* Children = InWidget->GetChildren();
				for (int32 i = 0; i < Children->Num(); ++i)
				{
					TSharedPtr<SWidget> ChildWidget = Children->GetChildAt(i);
					TSharedPtr<SWidget> FoucusableWidget = GetKeyboardFocusableWidget(ChildWidget);
					if (FoucusableWidget.IsValid() && EVisibility::DoesVisibilityPassFilter(FoucusableWidget->GetVisibility(), EVisibility::Visible))
					{
						return FoucusableWidget;
					}
				}
			}
		}
		return nullptr;
	}

	int32 SCollisionScrollBox_List::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
	{
		int32 NewLayerId = SCompoundWidget::OnPaint( Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled );

		if( !bShowSoftwareCursor )
		{
			return NewLayerId;
		}

		const FSlateBrush* Brush = FCoreStyle::Get().GetBrush(TEXT("SoftwareCursor_Grab"));

		FSlateDrawElement::MakeBox(
			OutDrawElements,
			++NewLayerId,
			AllottedGeometry.ToPaintGeometry( SoftwareCursorPosition - ( Brush->ImageSize / 2 ), Brush->ImageSize ),
			Brush
		);

		return NewLayerId;
	}

	void SCollisionScrollBox_List::ScrollBar_OnUserScrolled( float InScrollOffsetFraction )
	{
		const float ContentSize = GetScrollComponentFromVector(ScrollPanel->GetDesiredSize());
		const FGeometry ScrollPanelGeometry = FindChildGeometry(CachedGeometry, ScrollPanel.ToSharedRef());

		// Clamp to max scroll offset
		DesiredScrollOffset = FMath::Min(InScrollOffsetFraction * ContentSize, ContentSize - GetScrollComponentFromVector(ScrollPanelGeometry.GetLocalSize()));
		OnUserScrolled.ExecuteIfBound(DesiredScrollOffset);

		Invalidate(EInvalidateWidget::Layout);
	}

	const float ShadowFadeDistance = 32.0f;

	FSlateColor SCollisionScrollBox_List::GetStartShadowOpacity() const
	{
		// The shadow should only be visible when the user needs a hint that they can scroll up.
		const float ShadowOpacity = FMath::Clamp( ScrollPanel->PhysicalOffset/ShadowFadeDistance, 0.0f, 1.0f);
	
		return FLinearColor(1.0f, 1.0f, 1.0f, ShadowOpacity);
	}

	FSlateColor SCollisionScrollBox_List::GetEndShadowOpacity() const
	{
		// The shadow should only be visible when the user needs a hint that they can scroll down.
		const float ShadowOpacity = (ScrollBar->DistanceFromBottom() * GetScrollComponentFromVector(ScrollPanel->GetDesiredSize()) / ShadowFadeDistance);
	
		return FLinearColor(1.0f, 1.0f, 1.0f, ShadowOpacity);
	}

	bool SCollisionScrollBox_List::CanUseInertialScroll(float ScrollAmount) const
	{
		const auto CurrentOverscroll = Overscroll.GetOverscroll(CachedGeometry);

		// We allow sampling for the inertial scroll if we are not in the overscroll region,
		// Or if we are scrolling outwards of the overscroll region
		return CurrentOverscroll == 0.f || FMath::Sign(CurrentOverscroll) != FMath::Sign(ScrollAmount);
	}

	EAllowOverscroll SCollisionScrollBox_List::GetAllowOverscroll() const
	{
		return AllowOverscroll;
	}

	void SCollisionScrollBox_List::SetAllowOverscroll(EAllowOverscroll NewAllowOverscroll)
	{
		AllowOverscroll = NewAllowOverscroll;

		if (AllowOverscroll == EAllowOverscroll::No)
		{
			Overscroll.ResetOverscroll();
		}
	}

	void SCollisionScrollBox_List::SetAnimateWheelScrolling(bool bInAnimateWheelScrolling)
	{
		bAnimateWheelScrolling = bInAnimateWheelScrolling;
	}

	void SCollisionScrollBox_List::SetWheelScrollMultiplier(float NewWheelScrollMultiplier)
	{
		WheelScrollMultiplier = NewWheelScrollMultiplier;
	}

	void SCollisionScrollBox_List::BeginInertialScrolling()
	{
		if ( !UpdateInertialScrollHandle.IsValid() )
		{
			bIsScrolling = true;
			bIsScrollingActiveTimerRegistered = true;
			UpdateInertialScrollHandle = RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateSP(this, &SCollisionScrollBox_List::UpdateInertialScroll));
			Invalidate(EInvalidateWidget::LayoutAndVolatility);
		}
	}

	void SCollisionScrollBox_List::EndInertialScrolling()
	{
		bIsScrolling = false;
		bIsScrollingActiveTimerRegistered = false;
		Invalidate(EInvalidateWidget::LayoutAndVolatility);
		if ( UpdateInertialScrollHandle.IsValid() )
		{
			UnRegisterActiveTimer(UpdateInertialScrollHandle.ToSharedRef());
			UpdateInertialScrollHandle.Reset();
		}

		// Zero the scroll velocity so the panel stops immediately on mouse down, even if the user does not drag
		InertialScrollManager.ClearScrollVelocity();
	}
}